package club.ximeng.sliceupload.upload.controller;

import club.ximeng.sliceupload.common.constants.FileConstant;
import club.ximeng.sliceupload.common.entity.ResponseResult;
import club.ximeng.sliceupload.upload.controller.vo.MergeFileVO;
import club.ximeng.sliceupload.upload.controller.vo.VerifyFileVO;
import club.ximeng.sliceupload.upload.dto.FileMergeParamDTO;
import club.ximeng.sliceupload.upload.dto.FileUploadParamDTO;
import club.ximeng.sliceupload.upload.dto.VerifyParamDTO;
import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 大文件分片上传
 *
 * @author liu xm
 * @date 2022-03-08 9:19
 */
@Validated
@Slf4j
@RequestMapping("/file")
@RestController
public class UploadController {

    @Resource
    private HttpServletRequest request;

    /**
     * 文件上传
     *
     * @param dto 上传参数
     * @return result
     * @throws IOException .
     */
    @PostMapping("/upload")
    public ResponseResult<String> upload(@Valid FileUploadParamDTO dto) throws IOException {
        log.info("开始接收上传文件=>[{}]", dto);
        // 文件夹
        final File file = FileUtil.mkdir(FileConstant.FILE_PATH + dto.getHash());
        dto.getChunk().transferTo(new File(file, "/" + dto.chunkName()));
        log.info("文件分片 [{}] 保存成功", dto.getHash());
        return ResponseResult.success("received file chunk");
    }

    /**
     * 文件合并
     *
     * @param dto 文件合并参数
     * @return 文件url
     */
    @PostMapping("/merge")
    public ResponseResult<MergeFileVO> merge(@Valid @RequestBody FileMergeParamDTO dto) {
        log.info("开始合并文件=>[{}]", dto);
        log.info("format==>[{}]", String.format(FileConstant.FILE_MERGE_PATH, dto.fullFileName()));
        final File fileMergePath = FileUtil.mkdir(FileConstant.FILE_MERGE_PATH);
        final File file = new File(fileMergePath, dto.fullFileName());
        log.info("file==>{}", file.getAbsolutePath());
        if (file.exists()) {
            final boolean delete = file.delete();
            log.info("delete result[{}]", delete);
        }
        // 分片文件
        FileInputStream fileInputStream = null;
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file, true);
            final byte[] bytes = new byte[1024];
            int len;
            for (int i = 0; i < dto.getChunkTotal(); i++) {
                // 读取第i个分片
                fileInputStream = new FileInputStream(
                        FileConstant.FILE_PATH + dto.getFileHash() + "\\" + dto.getFileHash() + "-" + i);
                while ((len = fileInputStream.read(bytes)) != -1) {
                    //一直追加到合并的新文件中
                    outputStream.write(bytes, 0, len);
                }
            }
        } catch (IOException e) {
            log.error("分片合并异常", e);
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                assert outputStream != null;
                outputStream.close();
                log.info("IO流关闭");
                System.gc();
            } catch (Exception e) {
                log.error("IO流关闭", e);
            }
        }
        log.info("合并分片结束");
        // 移除分片文件
        final boolean del = FileUtil.del(FileConstant.FILE_PATH + dto.getFileHash());
        log.info("分片文件移除{}", del ? "成功" : "失败");
        final MergeFileVO build = MergeFileVO.builder().fileName(dto.getFileName())
                .fileUrl(String.format("http://%s:%s/file/%s",
                        request.getServerName(), request.getServerPort(), dto.fullFileName())).build();
        return ResponseResult.success(build);
    }

    /**
     * 文件校验
     *
     * @param dto 文件校验参数
     * @return 校验结果
     */
    @PostMapping("/verify")
    public ResponseResult<VerifyFileVO> verifyFile(@Valid @RequestBody VerifyParamDTO dto) {
        log.info("开始校验文件=>[{}]", dto);
        final File fileSuccess = new File(
                String.format(FileConstant.FILE_MERGE_PATH + "%s.%s", dto.getFileHash(), dto.fileSuffix()));
        log.info("filePath:[{}]", fileSuccess.getAbsolutePath());
        final VerifyFileVO.VerifyFileVOBuilder builder = VerifyFileVO.builder();
        if (FileUtil.exist(fileSuccess)) {
            final String fileSuccessMd5 = SecureUtil.md5(fileSuccess);
            log.info("存在文件的Hash值为:[{}]\n传入的Hash值为:[{}]", fileSuccessMd5, dto.getFileHash());
            if (Objects.equals(fileSuccessMd5, dto.getFileHash())) {
                builder.shouldUpload(false).fileName(dto.getFileName())
                        .fileUrl(String.format("http://%s:%s/file/%s",
                                request.getServerName(), request.getServerPort(), dto.fullFileName()));
                return ResponseResult.success(builder.build());
            }
        }
        final File[] listFiles = FileUtil.mkdir(FileConstant.FILE_PATH + dto.getFileHash()).listFiles();
        final List<String> list = Objects.isNull(listFiles) ? Collections.emptyList()
                : Arrays.stream(listFiles).map(File::getName).collect(Collectors.toList());
        builder.shouldUpload(true).uploadedList(list);
        return ResponseResult.success(builder.build());
    }
}
